package com.change.copy.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.change.copy.common.WordException;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class WordDocumentUtil {

    /**
     * 解析文档文件
     *
     * @param file 文档文件
     * @return 文档内容
     */
    public static String parseWord(MultipartFile file) throws WordException {
        String wordTxt = "";
        InputStream stream = null;
        try {
            if (file.getOriginalFilename() != null && file.getOriginalFilename().endsWith(".doc")) {
                stream = file.getInputStream();
                // Apache Poi
                WordExtractor ex = new WordExtractor(stream);
                wordTxt = ex.getText();
            } else if (file.getOriginalFilename() != null && file.getOriginalFilename().endsWith(".docx")) {
                stream = file.getInputStream();
                // EasyPoi
                XWPFDocument document = new XWPFDocument(stream);
                XWPFWordExtractor ex = new XWPFWordExtractor(document);
                wordTxt = ex.getText();
            } else {
                log.error("parseWord 文件错误");
            }
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    log.error("parseWord 文档流关闭有误", e);
                    throw new WordException("文档流关闭有误");
                }
            }
        } catch (Exception e) {
            log.error("parseWord 文档解析有误", e);
            throw new WordException("文档解析有误");
        }
        return wordTxt;
    }

    /**
     * 解析本地文档文件
     */
    public static String parseWord(String fileAddress) throws Exception {
        File wordFile = new File(fileAddress);
        String wordTxt = "";
        InputStream stream = null;
        try {
            if (wordFile.getName().endsWith(".doc")) {
                stream = Files.newInputStream(wordFile.toPath());
                // Apache Poi
                WordExtractor ex = new WordExtractor(stream);
                wordTxt = ex.getText();
            } else if (wordFile.getName().endsWith(".docx")) {
                stream = Files.newInputStream(wordFile.toPath());
                // EasyPoi
                XWPFDocument document = new XWPFDocument(stream);
                XWPFWordExtractor ex = new XWPFWordExtractor(document);
                wordTxt = ex.getText();
            } else {
                log.error("parseWord 文件错误");
            }
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    log.error("parseWord 文档流关闭有误", e);
                    throw new WordException("文档流关闭有误");
                }
            }
        } catch (Exception e) {
            log.error("parseWord 文档解析有误", e);
            throw new WordException("文档解析有误");
        }
        return wordTxt;
    }

    /**
     * 判断文档类型进行不同的分割方式
     * ".doc"后缀需要以"\r\n"切割 而".docx"后缀需要以"\n"切割
     *
     * @param file    文件名：以file.getOriginalFilename()传入
     * @param wordTxt 文件内容
     * @return 内容数组
     */
    public static String[] judgeType(String file, String wordTxt) {
        boolean suffixFlag = file.endsWith(".doc");

        return suffixFlag ? Arrays.stream(wordTxt.split("\r\n")).toArray(String[]::new)
                : Arrays.stream(wordTxt.split("\n")).toArray(String[]::new);
    }

    /**
     * 导出resources下的word模板表
     *
     * @param fileName 文件名
     * @param response {@link HttpServletResponse}
     */
    public void exportTemplate(String fileName, HttpServletResponse response) {
        InputStream inputStream = null;
        try {
            String path = "/word/" + fileName;
            inputStream = this.getClass().getResourceAsStream(path);

            String newFileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(fileName);

            byte[] bytes = new byte[1024 * 1024];
            // 输入流读取文件
            if (inputStream != null) {
                inputStream.read(bytes);
            }

            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/msword");
            response.setHeader("Access-Control-Expose-Headers", "Content-disposition");
            response.setHeader("Content-Disposition", "attachment;filename=" + newFileName);
            response.getOutputStream().write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 导出文件到本地
     */
    public static void exportLocalDirectory(List<String> texts, String outputAddress, String wordName, List<String> keyWords, boolean currentDirectory) {
        // 关键词排序
        keyWords = keyWords
                .stream()
                .sorted(Comparator.comparingInt(String::length).reversed())
                .map(String::trim)
                .distinct()
                .collect(Collectors.toList());
        // 输入文件目录
        String outputFileAddress = null;
        if (currentDirectory) {
            outputFileAddress = fileExist(System.getProperty("user.dir") + "\\src\\main\\resources\\" + wordName);
        } else {
            outputFileAddress = fileExist(outputAddress + "/" + wordName);
        }
        try {
            OutputStream fos = Files.newOutputStream(Paths.get(outputFileAddress));
            // 创建文档
            XWPFDocument document = new XWPFDocument();
            // 创建段落
            XWPFParagraph paragraph = document.createParagraph();
            //设置段落居中
            paragraph.setAlignment(ParagraphAlignment.LEFT);
            //设置行距为传进来的size
//            setSingleLineSpacing(paragraph, 28);
            //设置段落大纲等级
//            setOutLineLevel(paragraph, 0, "Heading0");
            for (String text : texts) {
                // 获取段落文本对象
                XWPFRun run = null;
                String[] split = null;
                // 替换
                Map<String, String> map = new HashMap<>();
                int count = 1;
                for (String keyWord : keyWords) {
                    if (text.contains(keyWord)) {
                        text = text.replace(keyWord, "${" + count + "}");
                        map.put("${" + count + "}", keyWord);
                        count++;
                    }
                }
                // 分割
                for (Map.Entry<String, String> entry : map.entrySet()) {
                    text = text.replace(entry.getKey(), "," + entry.getValue() + ",");
                }
                // 分组
                split = text.split(",");
                for (String t : split) {
                    if (keyWords.contains(t)) {
                        run = paragraph.createRun();
                        run.setText(t);
                        // 颜色
                        run.setColor("ff0000");
                        run.setFontSize(10);
                        run.setFontFamily("宋体");
                    } else {
                        run = paragraph.createRun();
                        run.setText(t);
                        run.setFontSize(10);
                        run.setFontFamily("宋体");
                    }
                }
                assert run != null;
                // 换行
                run.addBreak();
            }
            //将缓冲区的内容刷一下
            fos.flush();
            //将输出流中的内容写入word中
            document.write(fos);
        } catch (Exception e) {
            log.error("exportLocalDirectory 报错", e);
        }


    }


    private static CTSpacing setSingleLineSpacing(XWPFParagraph paragraph, int size) {
        CTP ctp = paragraph.getCTP();
        CTPPr ctpPr = ctp.isSetPPr() ? ctp.getPPr() : ctp.addNewPPr();
        CTSpacing ctSpacing = ctpPr.isSetSpacing() ? ctpPr.getSpacing() : ctpPr.addNewSpacing();
        //设置文本缩进，文本之前缩进0
        ctSpacing.setBefore(BigInteger.ZERO);
        //设置文本缩进，文本之后缩进0
        ctSpacing.setAfter(BigInteger.ZERO);
        //设置行间距规则为精确值
        ctSpacing.setLineRule(STLineSpacingRule.EXACT);
        //设置行间距值，单位磅
        ctSpacing.setLine(BigInteger.valueOf(size * 20L));
        return ctSpacing;
    }


    private static void setOutLineLevel(XWPFParagraph paragraph, int level, String styleId) {
        //获取设置样式对象
        CTStyle ctStyle = CTStyle.Factory.newInstance();
        //设置样式等级名称
        ctStyle.setStyleId(styleId);
        CTP ctp = paragraph.getCTP();
        CTPPr ctpPr = ctp.isSetPPr() ? ctp.getPPr() : ctp.addNewPPr();
        CTDecimalNumber ctDecimalNumber = ctpPr.isSetOutlineLvl() ? ctpPr.getOutlineLvl() : ctpPr.addNewOutlineLvl();
        //设置大纲等级，不设置默认为正文文本，设置的话0对应大纲等级一级
        ctDecimalNumber.setVal(BigInteger.valueOf(level));
        ctpPr.setOutlineLvl(ctDecimalNumber);
        ctStyle.setPPr(ctpPr);
        XWPFStyle style = new XWPFStyle(ctStyle);
        //设置应用于段落
        style.setType(STStyleType.PARAGRAPH);
    }

    /**
     * 判断文件是否存在
     * 返回不存在的路径文件名
     */
    private static String fileExist(String fileAddress) {
        int index = 1;
        String newFileAddress = fileAddress + index + ".docx";
        while (FileUtil.exist(newFileAddress)) {
            newFileAddress = fileAddress + ++index + ".docx";
        }
        return newFileAddress;
    }

    private static void setTableCenter(XWPFTableCell cell) {
        CTTc ctTc = cell.getCTTc();
        CTTcPr ctTcPr = ctTc.isSetTcPr() ? ctTc.getTcPr() : ctTc.addNewTcPr();
        CTVerticalJc ctVerticalJc = ctTcPr.isSetVAlign() ? ctTcPr.getVAlign() : ctTcPr.addNewVAlign();
        //设置单元格垂直对齐方式为居中
        ctVerticalJc.setVal(STVerticalJc.CENTER);
        CTP ctp = ctTc.getPList().get(0);
        CTPPr ctpPr = ctp.isSetPPr() ? ctp.getPPr() : ctp.addNewPPr();
        CTJc ctJc = ctpPr.isSetJc() ? ctpPr.getJc() : ctpPr.addNewJc();
        //设置水平居中
        ctJc.setVal(STJc.CENTER);
    }

    private static XWPFTable creatTable(XWPFDocument doc) {
        //基本信息表格
        XWPFTable table = doc.createTable();
        //设置表格居中
        table.setTableAlignment(TableRowAlign.CENTER);
        //增加表格边框
        CTTblBorders borders = table.getCTTbl().getTblPr().addNewTblBorders();
        setBorder(borders);
        //列宽自动分割
        CTTblWidth infoTableWidth = table.getCTTbl().addNewTblPr().addNewTblW();
        infoTableWidth.setType(STTblWidth.DXA);
        //设置表的列宽
        infoTableWidth.setW(BigInteger.valueOf(9072));
        return table;
    }

    private static void setBorder(CTTblBorders borders) {
        CTBorder hBorder = borders.addNewInsideH();
        hBorder.setVal(STBorder.SINGLE);  // 线条类型
        hBorder.setSz(BigInteger.ONE); // 线条大小
        hBorder.setColor("000000"); // 设置颜色

        CTBorder vBorder = borders.addNewInsideV();
        vBorder.setVal(STBorder.SINGLE);
        vBorder.setSz(BigInteger.ONE);
        vBorder.setColor("000000");

        CTBorder lBorder = borders.addNewLeft();
        lBorder.setVal(STBorder.SINGLE);
        lBorder.setSz(BigInteger.ONE);
        lBorder.setColor("000000");

        CTBorder rBorder = borders.addNewRight();
        rBorder.setVal(STBorder.SINGLE);
        rBorder.setSz(BigInteger.ONE);
        rBorder.setColor("000000");

        CTBorder tBorder = borders.addNewTop();
        tBorder.setVal(STBorder.SINGLE);
        tBorder.setSz(BigInteger.ONE);
        tBorder.setColor("000000");

        CTBorder bBorder = borders.addNewBottom();
        bBorder.setVal(STBorder.SINGLE);
        bBorder.setSz(BigInteger.ONE);
        bBorder.setColor("000000");
    }
}